package ec.fugu.amf;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * Amf0Serializer - Serialize a request in AMF0 Binary Format.
 * @author deathUser
 * 
 */
public class Amf0Serializer extends AmfSerializer {
	/**
	 * Constructor
	 * @param DataOutputStream stream
	 */
	public Amf0Serializer(DataOutputStream stream) {
		super(stream);
	}

	/**
	 * writeAMFData - Write to the OutputStream diferent type of Data, if forceType is not null, forces the use of the Write Method 
	 * @param Byte forceType 
	 */
	public boolean writeAMFData(Object data, Byte forcetype) throws IOException {
		boolean flagType = false;
		boolean returnValue = false; 
		byte type = Amf0Constants.DT_UNDEFINED;

		if(forcetype == null){
			//TODO
			if(data == null){
				type = Amf0Constants.DT_NULL;
				flagType = true;
			}
			if(!flagType && data instanceof Boolean){
				type = Amf0Constants.DT_BOOL;
				flagType = true;
			}
			if(!flagType && data instanceof Float || data instanceof Integer){
				type = Amf0Constants.DT_NUMBER;
				flagType = true;
			}
			if(!flagType && data instanceof String && data.toString().length() > Integer.MAX_VALUE){
				type = Amf0Constants.DT_LONGSTRING;
				flagType = true;
			}
			if(!flagType && data instanceof String){
				type = Amf0Constants.DT_STRING;
				flagType = true;
			}
			if(!flagType && data.getClass().isArray()){
				type = Amf0Constants.DT_ARRAY;
				flagType = true;
			}
			if(!flagType && data instanceof Hashtable){
				type = Amf0Constants.DT_MIXEDARRAY;
				flagType = true;
			}
			if(!flagType){
				if(data instanceof Amf3Wrapper)
					type = Amf0Constants.DT_AMF3;
				else if(data instanceof Date)
					type = Amf0Constants.DT_DATE;
				else 
					type = Amf0Constants.DT_OBJECT;
			}
		} else {
			type = forcetype.byteValue();
		}
		this.outputStream.write(type);
		switch (type) {
			case Amf0Constants.DT_NULL: return true; 
			case Amf0Constants.DT_BOOL: this.outputStream.writeBoolean(((Boolean)data).booleanValue());
										returnValue = true;
										break;
			case Amf0Constants.DT_NUMBER: this.outputStream.writeDouble(Double.parseDouble(data.toString()));
										returnValue = true;
										break;
			case Amf0Constants.DT_LONGSTRING: 
			case Amf0Constants.DT_STRING: returnValue = this.writeString(data.toString());
										break;
			case Amf0Constants.DT_DATE: this.outputStream.writeDouble(Double.parseDouble(new Long(((Date)data).getTime()*1000).toString()));
										this.outputStream.writeShort(0); //Empty timezone
										returnValue = true;
										break;
			case Amf0Constants.DT_ARRAY: returnValue = this.writeArray(data);
										break;
			case Amf0Constants.DT_MIXEDARRAY: returnValue = this.writeHashTable(data);
										break;
			case Amf0Constants.DT_OBJECT: returnValue = this.writeObject(data);
										break;
			case Amf0Constants.DT_AMF3: returnValue = this.writeAMF3Data(data);
										break;
			
			default: throw new IOException("Unsupported type: " +data.getClass().getName());
		}
		return returnValue;
	}
	
	public boolean writeString(String data) throws IOException{
		boolean returnValue=true;
		this.outputStream.writeUTF(data);
		return returnValue;
	}

	public boolean writeArray(Object data) throws IOException{
		boolean returnValue = false;
		Object[] values = (Object[])data;
		if(values.length==0){
			this.outputStream.writeInt(0);
			returnValue = true;
		} else {
			this.outputStream.writeInt(values.length);
			for(int i=0; i<values.length; i++){
				this.writeAMFData(values[i], null);
			}
			returnValue = true;
		}
		return returnValue;
	}

	public boolean writeHashTable(Object data) throws IOException {
		boolean returnValue = true;
		Hashtable ht = (Hashtable)data;
		this.outputStream.writeInt(0);
		//If we need to preserve the original order of the keys
		if(ht.containsKey("__keys__")){
			Vector keys = (Vector)ht.get("__keys__");
			Enumeration e = keys.elements();
			while(e.hasMoreElements()){
				String key = (String)e.nextElement();
				this.writeString(key);
				this.writeAMFData(ht.get(key), null);
			}
		} else {
			Enumeration e = ht.keys();
			while(e.hasMoreElements()){
				String key = (String)e.nextElement();
				this.writeString(key);
				this.writeAMFData(ht.get(key), null);
			}
		}
		this.writeString("");
		this.outputStream.write(Amf0Constants.DT_OBJECTTERM);
		return returnValue;
	}

	public boolean writeAMF3Data(Object data) throws IOException{
		Amf3Serializer serializer = new Amf3Serializer(this.outputStream);
		return serializer.writeAMFData(data, null);
	}

	//TODO: unimplemented
	public boolean writeObject(Object data){
		boolean returnValue = false;
		
		return returnValue;
	}
}